package org.theqcp.wow.sim.gui.tabs;

import java.awt.Color;
import java.awt.Component;
import java.awt.Cursor;
import java.awt.Graphics;
import java.awt.Point;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.datatransfer.UnsupportedFlavorException;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DragGestureEvent;
import java.awt.dnd.DragGestureListener;
import java.awt.dnd.DragSource;
import java.awt.dnd.DragSourceDragEvent;
import java.awt.dnd.DragSourceDropEvent;
import java.awt.dnd.DragSourceEvent;
import java.awt.dnd.DragSourceListener;
import java.awt.dnd.DropTarget;
import java.awt.dnd.DropTargetDragEvent;
import java.awt.dnd.DropTargetDropEvent;
import java.awt.dnd.DropTargetEvent;
import java.awt.dnd.DropTargetListener;
import java.io.IOException;

import javax.swing.DefaultListCellRenderer;
import javax.swing.DefaultListModel;
import javax.swing.JList;

public class ReorderableJList extends JList implements DragSourceListener, DropTargetListener, DragGestureListener {

    private static final long serialVersionUID = -2919431267790428036L;

    private class RJLTransferable implements Transferable {
	Object object;

	public RJLTransferable(Object o) {
	    object = o;
	}

	public Object getTransferData( DataFlavor df ) throws UnsupportedFlavorException, IOException {
	    if( isDataFlavorSupported(df) )
		return object;
	    else
		throw new UnsupportedFlavorException(df);
	}

	public boolean isDataFlavorSupported( DataFlavor df ) {
	    return (df.equals(localObjectFlavor));
	}

	public DataFlavor[] getTransferDataFlavors() {
	    return supportedFlavors;
	}
    }

    private class ReorderableListCellRenderer extends DefaultListCellRenderer {
	private static final long serialVersionUID = 1687982395195060512L;
	boolean isTargetCell;
	boolean isLastItem;

	public ReorderableListCellRenderer() {
	    super();
	}

	public Component getListCellRendererComponent( JList list, Object value, int index, boolean isSelected,
		boolean hasFocus ) {
	    isTargetCell = (value == dropTargetCell);
	    isLastItem = (index == list.getModel().getSize() - 1);
	    boolean showSelected = isSelected & (dropTargetCell == null);
	    return super.getListCellRendererComponent(list, value, index, showSelected, hasFocus);
	}

	public void paintComponent( Graphics g ) {
	    super.paintComponent(g);
	    if( isTargetCell ) {
		g.setColor(Color.black);
		g.drawLine(0, 0, getSize().width, 0);
	    }
	}
    }

    private final DataFlavor[] supportedFlavors;
    private final DataFlavor localObjectFlavor;

    private DragSource dragSource;
    private DropTarget dropTarget;
    private Object dropTargetCell;
    private int draggedIndex = -1;

    protected ReorderableJList(String... olist) {
	super();

	try {
	    localObjectFlavor = new DataFlavor(DataFlavor.javaJVMLocalObjectMimeType);
	} catch( ClassNotFoundException e ) {
	    throw new RuntimeException(e.getMessage());
	}

	supportedFlavors = new DataFlavor[] { localObjectFlavor };

	setCellRenderer(new ReorderableListCellRenderer());

	dragSource = new DragSource();
	dragSource.createDefaultDragGestureRecognizer(this, DnDConstants.ACTION_MOVE, this);
	dropTarget = new DropTarget(this, this);

	if( olist.length > 0 ) {
	    DefaultListModel model = new DefaultListModel();

	    for( String o: olist ) {
		model.addElement(o);
	    }

	    setModel(model);
	}
    }

    // DragGestureListener
    public void dragGestureRecognized( DragGestureEvent dge ) {
	// System.out.println("dragGestureRecognized");
	// find object at this x,y
	Point clickPoint = dge.getDragOrigin();
	int index = locationToIndex(clickPoint);
	if( index == -1 )
	    return;
	Object target = getModel().getElementAt(index);
	Transferable trans = new RJLTransferable(target);
	draggedIndex = index;
	dragSource.startDrag(dge, Cursor.getDefaultCursor(), trans, this);
    }

    // DragSourceListener events
    public void dragDropEnd( DragSourceDropEvent dsde ) {
	// System.out.println("dragDropEnd()");
	dropTargetCell = null;
	draggedIndex = -1;
	repaint();
    }

    public void dragEnter( DragSourceDragEvent dsde ) {
    }

    public void dragExit( DragSourceEvent dse ) {
    }

    public void dragOver( DragSourceDragEvent dsde ) {
    }

    public void dropActionChanged( DragSourceDragEvent dsde ) {
    }

    // DropTargetListener events
    public void dragEnter( DropTargetDragEvent dtde ) {
	// System.out.println("dragEnter");
	if( dtde.getSource() != dropTarget )
	    dtde.rejectDrag();
	else {
	    dtde.acceptDrag(DnDConstants.ACTION_COPY_OR_MOVE);
	    // System.out.println("accepted dragEnter");
	}
    }

    public void dragExit( DropTargetEvent dte ) {
    }

    // dragOver() listed below
    // drop() listed below
    public void dropActionChanged( DropTargetDragEvent dtde ) {
    }

    @Override
    public void dragOver( DropTargetDragEvent dtde ) {
	// figure out which cell it's over, no drag to self
	if( dtde.getSource() != dropTarget )
	    dtde.rejectDrag();
	Point dragPoint = dtde.getLocation();
	int index = locationToIndex(dragPoint);
	if( index == -1 )
	    dropTargetCell = null;
	else
	    dropTargetCell = getModel().getElementAt(index);
	repaint();
    }

    @Override
    public void drop( DropTargetDropEvent dtde ) {
	// System.out.println("drop()!");
	if( dtde.getSource() != dropTarget ) {
	    // System.out.println("rejecting for bad source (" +
	    // dtde.getSource().getClass().getName() + ")");
	    dtde.rejectDrop();
	    return;
	}
	Point dropPoint = dtde.getLocation();
	int index = locationToIndex(dropPoint);
	// System.out.println("drop index is " + index);
	boolean dropped = false;
	try {
	    if( (index == -1) || (index == draggedIndex) ) {
		System.out.println("dropped onto self");
		dtde.rejectDrop();
		return;
	    }
	    dtde.acceptDrop(DnDConstants.ACTION_MOVE);
	    // System.out.println("accepted");
	    Object dragged = dtde.getTransferable().getTransferData(localObjectFlavor);
	    // move items - note that indicies for insert will
	    // change if [removed] source was before target
	    // System.out.println("drop " + draggedIndex + " to " + index);
	    boolean sourceBeforeTarget = (draggedIndex < index);
	    // System.out.println("source is" + (sourceBeforeTarget ? "" :
	    // " not") + " before target");
	    // System.out.println("insert at " + (sourceBeforeTarget ? index - 1
	    // : index));
	    DefaultListModel mod = (DefaultListModel) getModel();
	    mod.remove(draggedIndex);
	    mod.add((sourceBeforeTarget ? index - 1 : index), dragged);
	    dropped = true;
	} catch( Exception e ) {
	    e.printStackTrace();
	}
	dtde.dropComplete(dropped);
    }

}
